﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
using PacketDotNet;
using System.Net;
using System.Collections.Specialized;

/**
Nighthawk - ARP/ND spoofing, simple SSL stripping and password sniffing for Windows
 * Author: Wolfgang Christl

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

namespace Nighthawk
{
    public class History
    {
        public bool Started;

        private bool excludeLocalIP;
        private DeviceInfo deviceInfo;

        // packet queues (packet storage for BG threads)
        public List<TcpPacket> PacketQueueH = new List<TcpPacket>();
        public List<TcpPacket> ResponsePacketQueue = new List<TcpPacket>();
        private List<TcpPacket> threadQueueH = new List<TcpPacket>();
        private List<TcpPacket> ThreadResponsePacketQueue = new List<TcpPacket>();

        private List<string> UninterestingSiteStrings;

        private Regex regexIsTextHTML = new Regex(@"<html", RegexOptions.Compiled | RegexOptions.Singleline);
        private Regex regexGZip = new Regex(@"Content-Encoding: gzip", RegexOptions.Compiled | RegexOptions.Singleline);

        private UTF8Encoding encoding = new UTF8Encoding();

        private Thread worker;

        private byte[] decodedpayload = new byte[2000];
        // collection of POST requests (hostname/url tracking for incomplete packets)
        private List<HistoryPostRequest> postRequests;

        // events
        public event HistoryResultHandler HistoryResult;

        private void Result(string url, string text)
        {
            if (HistoryResult != null) HistoryResult(url, text);
        }

        public History(DeviceInfo deviceInfo)
        {
            this.deviceInfo = deviceInfo;
            UninterestingSiteStrings = new List<string>(Properties.Settings.Default.notinterestingsites.Cast<string>());
        }

        // start sniffer (exclude local traffic)
        public void Start(bool excludeLocalIP)
        {
            this.excludeLocalIP = excludeLocalIP;

            Started = true;
            UninterestingSiteStrings = new List<string>(Properties.Settings.Default.notinterestingsites.Cast<string>());

            postRequests = new List<HistoryPostRequest>();

            // start a worker thread
            worker = new Thread(new ThreadStart(Worker));
            worker.Name = "History thread";
            worker.Start();
        }

        // stop sniffer
        public void Stop()
        {
            Started = false;

            if(worker != null && worker.IsAlive) worker.Join();
        }
        
        // worker function for parsing packets
        public void Worker()
        {
            while (Started)
            {
                #region copy packets to threadQueue
                lock (PacketQueueH)
                {
                    foreach (TcpPacket packet in PacketQueueH)
                    {
                        threadQueueH.Add(packet);
                    }

                    PacketQueueH.Clear();
                }
                #endregion

                if (threadQueueH.Count > 0)
                {
                    // loop through packets and check them for any useful information
                    foreach (TcpPacket packet in threadQueueH)
                    {
                        if (packet.ParentPacket == null) continue;
                        
                        var sourceIP = ((IpPacket) packet.ParentPacket).SourceAddress;
                        var destIP = ((IpPacket) packet.ParentPacket).DestinationAddress;

                        // check for exclusions
                        if (excludeLocalIP)
                        {
                            if (sourceIP.ToString() == deviceInfo.IP || deviceInfo.IPv6List.Contains(sourceIP.ToString()))
                            {
                                continue;
                            }
                        }

                        // check for HTTP packet and parse it
                        if (HttpPacket.IsHTTP(packet))
                        {
                            var http = new HttpPacket(packet);

                            // save hostnames for incomplete packets
                            if (http.Header.Type == HttpHeader.PacketType.Request && http.Header.ReqType == HttpHeader.RequestType.POST)
                            {
                                postRequests.Add(new HistoryPostRequest {SourceAddress = sourceIP, DestinationAddress = destIP, Hostname = http.Header.Host});
                            }
                            #region get rid of stuff we are not interested in
                            //Not interested in Responses and fuck POST man ...
                            if (http.Header.IsRequest()==false || http.Header.POST==true) continue;

                            //HistorySearch(http, sourceIP, destIP);
                            if (http.Header.Host == "")
                            {
                                //TCP Packet is not interesting
                                //System.Diagnostics.Debug.WriteLine("No Host found");
                                continue;
                            }
                            //Remove all Images (.ico .jpg .png .gif) and Javascript
                            if (http.IsImageRequest() || http.IsJavaScriptRequest()) continue;

                            //Only get HTML Requests (no java content etc.)
                            if (http.IsHTMLRequest() == false) continue;

                            //System.Diagnostics.Debug.WriteLine("Passing Host to UI: " + http.Header.Host + http.Header.Path);
                            string HttpPath = http.Header.Host + http.Header.Path;

                            //getting rid of all annoying Packets
                            bool isannoying = false;
                            foreach (string unerwunscht in UninterestingSiteStrings)
                            {
                                if (HttpPath.Contains(unerwunscht) == true)
                                {
                                    isannoying = true;
                                }
                            }
                            if (isannoying == true) continue;
                            #endregion

                            #region HTTPCheckBack
                            ushort sourceport = packet.SourcePort;

                            System.Diagnostics.Debug.WriteLine("");
                            System.Diagnostics.Debug.WriteLine("TCP Payload Length: "+ packet.PayloadData.Length+" , Packet SQN number: "+packet.SequenceNumber);
                            
                            uint nextSQNRES = packet.AcknowledgmentNumber;

                            Thread.Sleep(150); //Wait so the Response packets can get into the ResponsePacketQueue
                            #region copy requestpackets so we can process them
                            lock (ResponsePacketQueue)
                            {
                                foreach (TcpPacket tcprespacket in ResponsePacketQueue)
                                {
                                    ThreadResponsePacketQueue.Add(tcprespacket);
                                }

                                ResponsePacketQueue.Clear();
                            }
                            System.Diagnostics.Debug.WriteLine("HTTPCheckBack: Seach for Responses for " + http.Header.Host + http.Header.Path);
                            System.Diagnostics.Debug.WriteLine("HTTPCheckBack: Scanning " + ThreadResponsePacketQueue.Count + " packets for SQN: " + nextSQNRES);
                            #endregion

                            #region Scan all Responsepackets
                            foreach (TcpPacket ResPacket in ThreadResponsePacketQueue){
                                //Find the start(s) of TCP Stream(s) (one TCP-Stream per port and IP at a time)
                                System.Diagnostics.Debug.WriteLine("HTTPCheckBack: Packet: SQN=" + ResPacket.SequenceNumber + " DstPort=" + ResPacket.DestinationPort);
                                if (ResPacket.DestinationPort != sourceport || nextSQNRES != ResPacket.SequenceNumber || !ResPacket.ValidChecksum || ResPacket.PayloadData == null) continue;

                                System.Diagnostics.Debug.WriteLine("HTTPCheckBack: Responses to SQN " + nextSQNRES + " found!");
                               
                                //Check Gzip
                                if (SimpleRegex.GetMatches(regexGZip, encoding.GetString(ResPacket.PayloadData)).Count > 0)
                                {
                                    System.Diagnostics.Debug.Write(" -> Gzip encoded");
                                    HttpResponseManagement HRM = new HttpResponseManagement();

                                    lock (HRM)
                                    {
                                        decodedpayload = HRM.CheckBack(ResPacket.SequenceNumber, ResPacket.DestinationPort, http.Header.Host);
                                    }
                                    //byte[] decodedpayload = HRM.CheckBack(ResPacket.SequenceNumber, ResPacket.SourcePort, http.Header.Host);
                                    PacketDotNet.Utils.ByteArraySegment bas = new PacketDotNet.Utils.ByteArraySegment(decodedpayload);
                                    HttpPacket httpgzip = new HttpPacket(new TcpPacket(bas));
                                    Result(httpgzip.Header.Host + httpgzip.Header.Path, httpgzip.Header.Host + httpgzip.Header.Path + " (Gzip)");
                                    break;
                                }

                                if (SimpleRegex.GetMatches(regexIsTextHTML, encoding.GetString(ResPacket.PayloadData)).Count > 0)
                                {
                                    System.Diagnostics.Debug.WriteLine("HTTPCheckBack: Response is valid");
                                    System.Diagnostics.Debug.WriteLine("Response: " + encoding.GetString(ResPacket.PayloadData));
                                    Result(http.Header.Host + http.Header.Path, http.Header.Host + http.Header.Path+" (Anzeige)");
                                    break;
                                }
                                else
                                {
                                    System.Diagnostics.Debug.WriteLine("HTTPCheckBack: Response is not valid");
                                    System.Diagnostics.Debug.WriteLine("Response: " + encoding.GetString(ResPacket.PayloadData));
                                    continue;
                                }
                            }
                            #endregion
                            #endregion
                        }
                    }

                    threadQueueH.Clear();
                    ThreadResponsePacketQueue.Clear();
                }
                else
                {
                    Thread.Sleep(50);
                }
            }

            return;
        }
    }

    public class HistoryPostRequest
    {
        public IPAddress SourceAddress;
        public IPAddress DestinationAddress;
        public uint SequenceNumber;
        public string Hostname;
    }

    public class HistoryElement
    {
        public IPAddress SourceAddress;
        public IPAddress DestinationAddress;
        public string Adress;
    }
    
    public delegate void HistoryResultHandler(string url, string stext);
}
